﻿/*
Copyright (c) 2011, Pawel Szczurek
All rights reserved.


Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:


Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without
specific prior written permission.


THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Alice.VM;
using Alice.Code;
using Alice.Script;

namespace Alice.Compiler
{
    public class CodeGenerator
    {
        public static readonly string ArgCountVarName = "arg count";
        public static readonly string ReturnAddressVarName = "return address";

        public struct JumpFinisher
        {
            internal JumpFinisher(List<byte> program)
            {
                m_program = program;
                m_bytes = 4;

                m_program.Add((byte)OPCodes.LoadArg32);

                //prepare space
                m_offset = m_program.Count;
                for (int i = 0; i < m_bytes; i++)
                    program.Add(0);
            }

            public void Finish()
            {
                //" - m_bytes - 1" because m_offset is locaded before jump instruction
                Int32 offset = m_program.Count - m_offset - m_bytes - 1;
                byte[] b;

                switch (m_bytes)
                {
                    case 1:
                        m_program[m_offset] = (byte)((sbyte)offset);
                        break;

                    case 2:
                        b = BitConverter.GetBytes((Int16)offset);
                        m_program[m_offset] = b[0];
                        m_program[m_offset + 1] = b[1];
                        break;

                    case 4:
                        b = BitConverter.GetBytes((Int32)offset);
                        m_program[m_offset] = b[0];
                        m_program[m_offset + 1] = b[1];
                        m_program[m_offset + 2] = b[2];
                        m_program[m_offset + 3] = b[3];
                        break;

                    default:
                        throw new Exception("unsupported bit count in jump finisher");
                }
            }

            #region members

            Int32 m_offset;
            List<byte> m_program;
            int m_bytes;

            #endregion
        }

        public struct Label
        {
            internal Label(List<byte> program)
            {
                Position = program.Count;
            }

            public readonly Int32 Position;
        }

        public class Scope
        {
            internal Scope(CodeGenerator cg, bool function, int argCount)
            {
                BeginStackTop = cg.RuntimeStackTop();
                m_cg = cg;
                Function = function;
                m_argCount = argCount;

                m_cg.m_variables.BeginScope(function);
            }

            internal void Finish()
            {
                Finish(false);
            }

            internal void Finish(bool returnOnly)
            {
                if(!returnOnly)
                    m_cg.EndScope(this);

                int popCount = m_cg.RuntimeStackTop() - BeginStackTop;

                if (!Function)
                {
                    Debug.Assert(!returnOnly);
                    //pop all values from stack
                    m_cg.Pop(popCount);
                }
                else
                {
                    // (return value, return address, arg count, args)
                    Debug.Assert(popCount - 3 - m_argCount >= 0);
                    m_cg.LoadArg(popCount - 3 - m_argCount);
                    //form "static" point of view it looks like call ret poped just return value and arg count from stack;
                    m_cg.EmitOpCode(OPCodes.CallRet, -2);   
                }
            }

            internal readonly int BeginStackTop;
            readonly CodeGenerator m_cg;
            internal readonly bool Function;
            readonly int m_argCount;
        }

      /*  public enum BinaryOperatorOptions
        {
            Equal,
            NotEqual,
            Less,
            LessEqual,
            Greater,
            GreaterEqual,

            Add,
            Sub,
            Mul,
            Div,
            Mod
        }*/

        public CodeGenerator()
        {
            m_variables = new VariableManager(this);
        }

        public Scope BeginFunctionScope(IdentifierList constParameters, IdentifierList parameters)
        {
            int count = 0;

            if (constParameters != null)
                count += constParameters.List.Count;
            if (parameters != null)
                count += parameters.List.Count;

            //when scope finises, leaves function pointer on stack
            Scope s = new Scope(this, true, count);

            //define params            
            if (parameters != null)
            {
                for (int i = parameters.List.Count - 1; i >= 0; i--)
                {
                    ChangeRuntimeStack(+1);
                    DefineVar(parameters.List[i].Name);
                }
            }

            //define const param
            if (constParameters != null)
            {
                for (int i = constParameters.List.Count - 1; i >= 0 ; i--)
                {
                    ChangeRuntimeStack(+1);
                    DefineVar(constParameters.List[i].Name);
                }
            }

            ChangeRuntimeStack(+1);
            DefineVar(ArgCountVarName);

            ChangeRuntimeStack(+1);
            DefineVar(ReturnAddressVarName);

            m_scopeStack.Add(s);

            return s;
        }

       /* public Scope BeginFunctionScope(int argCount)
        {
            Scope s = new Scope(this, true, argCount);
            return s;
        }*/

        public Scope BeginScope()
        {
            //m_variables.BeginScope(separated);
            Scope s = new Scope(this, false, -1);

            m_scopeStack.Add(s);

            return s;
        }

        void EndScope(Scope s)
        {
            Debug.Assert(m_scopeStack.Count > 0 && m_scopeStack[m_scopeStack.Count-1] == s);

            m_variables.EndScope(s.Function);

            m_scopeStack.RemoveAt(m_scopeStack.Count-1);
        }

     /*   public void EndScope()
        {
            m_variables.EndScope();
        }*/
        public void DefineVar(string name)
        {
            //LoadSpecial(SpecialValues.Undefined);
            m_variables.DefineVar(name);
        }
        /// <summary>
        /// stack +1
        /// </summary>
        /// <param name="arg"></param>
        public void LoadArg(int arg)
        {
            if (arg <= sbyte.MaxValue && arg >= sbyte.MinValue)
            {
                m_program.Add((byte)OPCodes.LoadArg8);
                m_program.Add((byte)((sbyte)arg));
            }
            else if (arg <= Int16.MaxValue && arg >= Int16.MinValue)
            {
                m_program.Add((byte)OPCodes.LoadArg16);
                m_program.AddRange(BitConverter.GetBytes((Int16)arg));
            }
            else if (arg <= Int32.MaxValue && arg >= Int32.MinValue)
            {
                m_program.Add((byte)OPCodes.LoadArg32);
                m_program.AddRange(BitConverter.GetBytes((Int32)arg));
            }
           /* else
            {
                m_program.Add((byte)OPCodes.LoadArg64);
                m_program.AddRange(BitConverter.GetBytes(arg));
            }*/

            ChangeRuntimeStack(+1);
        }
        /// <summary>
        /// stack +1
        /// </summary>
        /// <param name="name"></param>
        public void LoadVar(string name)
        {
            m_variables.LoadVar(name);
        }
        /// <summary>
        /// stack +1
        /// </summary>
        /// <param name="value"></param>
        public void LoadConst(string value)
        {
            //create const
            int position = m_consts.CreateConst(value);

            //load const id
            LoadArg(position);

            //load const
            m_program.Add((byte)OPCodes.LoadConst);
            //ChangeRuntimeStack(+0);
        }
        public void LoadIP(int ip)
        {
            //load ip
            LoadArg(ip);

            //convert to ip
            m_program.Add((byte)OPCodes.LoadIP);

            //ChangeRuntimeStack(+0);
        }
        /// <summary>
        /// stack +1
        /// </summary>
        /// <param name="value"></param>
        public void LoadConst(double value)
        {
            //create const
            int position = m_consts.CreateConst(value);

            //load const id
            LoadArg(position);

            //load const
            m_program.Add((byte)OPCodes.LoadConst);
            //ChangeRuntimeStack(+0);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sv"></param>
        public void LoadSpecial(SpecialValues sv)
        {
            //load const id
            LoadArg((int)sv);

            //load const
            m_program.Add((byte)OPCodes.LoadSpecial);
            //ChangeRuntimeStack(+0);
        }
        public void RuntimeError(RuntimeError err)
        {
            //load const id
            LoadArg((int)err);         

            //load const
            m_program.Add((byte)OPCodes.RunTimeError);
            ChangeRuntimeStack(-1); //it dont generate value on stack, just remove arg

            //ChangeRuntimeStack(+0);
        }
        /// <summary>
        /// stack -1
        /// </summary>
        /// <param name="name"></param>
        public void StoreVar(string name)
        {
            m_variables.StoreVar(name);
        }
        /// <summary>
        /// stack -1
        /// </summary>
        /// <returns></returns>
        public JumpFinisher JumpIfFalse()
        {
            //load offset & jump
            JumpFinisher jf = new JumpFinisher(m_program);
            ChangeRuntimeStack(+1);

            m_program.Add((byte)OPCodes.JumpIfNotTrue);
            ChangeRuntimeStack(-2);

            return jf;
        }
        /// <summary>
        /// stack 0
        /// </summary>
        /// <returns></returns>
        public JumpFinisher Jump()
        {
            //load offset & jump
            JumpFinisher jf = new JumpFinisher(m_program);
            ChangeRuntimeStack(+1);

            m_program.Add((byte)OPCodes.Jump);
            ChangeRuntimeStack(-1);

            return jf;
        }
        /// <summary>
        /// stack 0
        /// </summary>
        /// <returns></returns>
        public void Jump(Label l)
        {
            int offset = l.Position - m_program.Count - 6;

           // LoadArg(offset);
            m_program.Add((byte)OPCodes.LoadArg32);
            m_program.AddRange(BitConverter.GetBytes((Int32)offset));
            ChangeRuntimeStack(1);

            //jump
            m_program.Add((byte)OPCodes.Jump);
            ChangeRuntimeStack(-1);
        }
        /// <summary>
        /// get value from object field
        /// value is left on stack
        /// </summary>
        /// <param name="name"></param>
        public void LoadField(string name)
        {
            //load field name
            LoadConst(name);

            //emit code
            m_program.Add((byte)OPCodes.GetObjectMember);

            ChangeRuntimeStack(-1);
        }
        /// <summary>
        /// store value to object field
        /// value will be left on stack
        /// </summary>
        /// <param name="name"></param>
        public void StoreField(string name)
        {
            //load field name
            LoadConst(name);

            //emit code
            m_program.Add((byte)OPCodes.SetObjectMember);

            ChangeRuntimeStack(-2);
        }
        public void BinaryOperator(Operator co)
        {
            switch (co)
            {
                //binary
                case Operator.Equal: m_program.Add((byte)OPCodes.Equal); break;
                //case Operator.NotEqual: m_program.Add((byte)OPCodes.NotEqual); break;
                case Operator.NotEqual: m_program.Add((byte)OPCodes.Equal); m_program.Add((byte)OPCodes.Not); break;
                case Operator.Less: m_program.Add((byte)OPCodes.Less); break;
                case Operator.LessEqual: m_program.Add((byte)OPCodes.LessEqual); break;
                case Operator.Greater: m_program.Add((byte)OPCodes.LessEqual); m_program.Add((byte)OPCodes.Not); break;
                case Operator.GreaterEqual: m_program.Add((byte)OPCodes.Less); m_program.Add((byte)OPCodes.Not); break;

                case Operator.Add: m_program.Add((byte)OPCodes.Add); break;
                case Operator.Sub: m_program.Add((byte)OPCodes.Sub); break;
                case Operator.Mul: m_program.Add((byte)OPCodes.Mul); break;
                case Operator.Div: m_program.Add((byte)OPCodes.Div); break;
                case Operator.Mod: m_program.Add((byte)OPCodes.Mod); break;

                //Assign
              /*  //case Operator.Assign: m_program.Add((byte)OPCodes.Set); break;
                case Operator.PlusAssign: m_program.Add((byte)OPCodes.Add); break;
                case Operator.MinusAssign: m_program.Add((byte)OPCodes.Sub); break;
                case Operator.MulAssign: m_program.Add((byte)OPCodes.Equal); break;
                case Operator.DivAssign: m_program.Add((byte)OPCodes.Equal); break;
                case Operator.ModAssign: m_program.Add((byte)OPCodes.Equal); break;*/

                //logic
                case Operator.LogicalAnd: m_program.Add((byte)OPCodes.And); break;
                case Operator.LogicalOr: m_program.Add((byte)OPCodes.Or); break;
                case Operator.LogicalXor: m_program.Add((byte)OPCodes.Xor); break;

                default: throw new Exception("operator not supported"); break;
            }

            ChangeRuntimeStack(-2 + 1);
        }
        public void UnaryOperator(Operator co)
        {
            switch (co)
            {
                //Unary
                case Operator.Not: m_program.Add((byte)OPCodes.Not); break;

                // case Operator.PostInc: m_program.Add((byte)OPCodes.Xor); break;
                // case Operator.PostDec: m_program.Add((byte)OPCodes.Xor); break;
                //case Operator.PreInc: m_program.Add((byte)OPCodes.Xor); break;
                //case Operator.PreDec: m_program.Add((byte)OPCodes.Xor); break;

                //case Operator.Plus: /* do nothing */ break;
                //case Operator.Minus: m_program.Add((byte)OPCodes.PrefixMinus); break;

                default: throw new Exception("operator not supported"); break;
            }

            ChangeRuntimeStack(-1 + 1);
        }
        /// <summary>
        /// stack -2, +1
        /// </summary>
        /// <param name="co"></param>
      /*  public void BinaryOperator(BinaryOperatorOptions co)
        {

            switch (co)
            {
                case BinaryOperatorOptions.Equal: m_program.Add((byte)OPCodes.Equal); break;
                case BinaryOperatorOptions.NotEqual: m_program.Add((byte)OPCodes.NotEqual); break;

                case BinaryOperatorOptions.Add: m_program.Add((byte)OPCodes.Add); break;
                case BinaryOperatorOptions.Sub: m_program.Add((byte)OPCodes.Sub); break;
                case BinaryOperatorOptions.Mul: m_program.Add((byte)OPCodes.Mul); break;
                case BinaryOperatorOptions.Div: m_program.Add((byte)OPCodes.Div); break;
                case BinaryOperatorOptions.Mod: m_program.Add((byte)OPCodes.Mod); break;

                default: throw new Exception("operator not supported"); break;
            }

            ChangeRuntimeStack(-2 + 1);
        }*/
        /// <summary>
        /// stack -1
        /// runtime stack -1(func), -1(argcount), -n(args), +1(ret)
        /// </summary>
        /// <param name="paramCount">just for debug</param>
        public void FunctionCall(int paramCount)
        {
            m_program.Add((byte)OPCodes.Call);

            ChangeRuntimeStack(-1 - 1 - paramCount + 1);
        }
        /// <summary>
        /// pop n values from stack
        /// </summary>
        /// <param name="n"></param>
        public void Pop(int n)
        {
            Debug.Assert(n >= 0);

            if (n == 0)
                return;

            if (n == 1)
            {
                m_program.Add((byte)OPCodes.Pop);
                ChangeRuntimeStack(-1);
            }
            else
            {
                //pops count
                LoadArg(n);

                //pop
                m_program.Add((byte)OPCodes.PopN);
                ChangeRuntimeStack(-n - 1);
            }
        }

        /// <summary>
        /// symulate runtime behaviour
        /// </summary>
        /// <returns></returns>
        public int RuntimeStackTop()
        {
            return m_runtimeStackTop;
        }

        /// <summary>
        /// create new program
        /// </summary>
        /// <returns></returns>
        public Program Finish()
        {
            Debug.Assert(m_variables.ScopesCount == 0);

            if (m_compiledProgram == null)
            {
               // m_program.Add((byte)OPCodes.Exit);
                m_compiledProgram = new Program(m_program, m_consts.ConstsList);
            }

            return m_compiledProgram;
        }

        /// <summary>
        /// create label at current place in code
        /// </summary>
        /// <returns></returns>
        public Label CreateLabel()
        {
            return new Label(m_program);
        }

        /// <summary>
        /// prepare code generator for reuse
        /// </summary>
        public void Restart()
        {
            m_runtimeStackTop = 0;
            m_program.Clear();
            m_consts.Clear();
            m_variables = new VariableManager(this);
            m_compiledProgram = null;
        }

        public void FunctionReturn(bool returnValueOnStack)
        {
            //if nothing returned push Undefined
            if(!returnValueOnStack)
                LoadSpecial(SpecialValues.Undefined); 

            //finish function
            for (int i = m_scopeStack.Count - 1; i >= 0; i--)
            {
                if (m_scopeStack[i].Function == true)
                {
                    m_scopeStack[i].Finish(true);
                    break;
                }
            }
        }

        internal void EmitOpCode(OPCodes o, int stackChange)
        {
            m_program.Add((byte)o);
            ChangeRuntimeStack(stackChange);
        }

        internal void ChangeRuntimeStack(int i)
        {
            m_runtimeStackTop += i;
        }

        public int InstructionId()
        {
            return m_program.Count;
        }

        #region private

        /// <summary>
        /// gives runtime stack top information
        /// </summary>
        /// <param name="i"></param>
        

        int m_runtimeStackTop = 0;
        readonly List<byte> m_program = new List<byte>();
        readonly ConstManager m_consts = new ConstManager();
        VariableManager m_variables;
        Program m_compiledProgram = null;

        List<Scope> m_scopeStack = new List<Scope>();

        #endregion
    }
}
